using DotNetCommon.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DotNetCommon.Logger;

internal class LoggerHelper
{
    internal static bool hasInitQueue = false;
    /// <summary>
    /// 单例
    /// </summary>
    public static readonly ILogger Instance = new InternalLogger<object>("");

    internal static BlockingCollection<LogContext> messageQueue = new BlockingCollection<LogContext>();
}
internal class InternalLogger<T> : ILogger, ILogger<T>
{
    internal InternalLogger(string categoryName)
    {
        this.CategoryName = categoryName;
        InitQueue();
    }

    /// <summary>
    /// 日志类别
    /// </summary>
    public string CategoryName { get; set; }
    private static void InitQueue()
    {
        if (!LoggerHelper.hasInitQueue)
        {
            lock (typeof(ILogger))
            {
                if (!LoggerHelper.hasInitQueue)
                {
                    LoggerHelper.hasInitQueue = true;
                    Task.Factory.StartNew(() =>
                    {
                        while (true)
                        {
                            try
                            {
                                var ctx = LoggerHelper.messageQueue.Take();
                                LogFile(ctx);
                            }
                            catch (Exception ex)
                            {
                                LogInternal(ex);
                            }
                        }
                    }, TaskCreationOptions.LongRunning);
                }
            }
        }
    }

    /// <summary>
    /// 记录日志
    /// </summary>
    /// <param name="categoryName"></param>
    /// <param name="level"></param>
    /// <param name="message"></param>
    /// <param name="exception"></param>
    public void Log(string categoryName, LoggerLevel level, string message, Exception exception = null)
    {
        var ctx = new LogContext
        {
            CategoryName = categoryName,
            Level = level,
            Message = message ?? exception?.Message,
            Time = DateTime.Now,
            Exception = exception
        };
        LoggerFactory.LogAction?.Invoke(ctx);
        if (LoggerFactory.LogAction == null || LoggerFactory.EnableDefaultOutPut) LoggerHelper.messageQueue.TryAdd(ctx);
    }

    public void LogTrace(string message) => Log(CategoryName, LoggerLevel.Trace, message, null);
    public void LogDebug(string message) => Log(CategoryName, LoggerLevel.Debug, message, null);
    public void LogInformation(string message) => Log(CategoryName, LoggerLevel.Information, message, null);
    public void LogWarning(string message) => Log(CategoryName, LoggerLevel.Warning, message, null);
    public void LogError(string message) => Log(CategoryName, LoggerLevel.Error, message, null);
    public void LogError(Exception exception) => Log(CategoryName, LoggerLevel.Error, null, exception);
    public void LogError(Exception exception, string message) => Log(CategoryName, LoggerLevel.Error, message, exception);
    public void LogCritical(string message) => Log(CategoryName, LoggerLevel.Critical, message, null);
    public void LogCritical(Exception exception) => Log(CategoryName, LoggerLevel.Critical, null, exception);
    public void LogCritical(Exception exception, string message) => Log(CategoryName, LoggerLevel.Critical, message, exception);

    private static string internalLogFileDir = "";
    static InternalLogger()
    {
        internalLogFileDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ".logs");
        if (!Directory.Exists(internalLogFileDir)) Directory.CreateDirectory(internalLogFileDir);
    }

    private static void LogInternal(Exception ex)
    {
        var filePath = Path.Combine(internalLogFileDir, "internal.log");
        var msg = $"{DateTime.Now.ToGlobalStampString()}    Message: {ex?.Message} StackTrace: {ex?.StackTrace}\r\nInnerException.Message: {ex?.InnerException?.Message}  InnerException.StackTrace: {ex?.InnerException?.StackTrace}";
        File.AppendAllLines(filePath, new string[] { msg });
    }

    private static void LogFile(LogContext logContext)
    {
        //是否可输出
        if (LoggerFactory.GetCanOutFunc != null)
        {
            if (!LoggerFactory.GetCanOutFunc(logContext)) return;
        }
        //构建默认的日志输出格式
        var sb = new StringBuilder();
        sb.Append(logContext.Time.ToGlobalStamp2String());
        sb.Append($" [{logContext.Level.ToDescription()}]");
        if (logContext.CategoryName.IsNotNullOrWhiteSpace()) sb.Append($" {logContext.CategoryName}:");
        sb.Append($" {logContext.Message}");
        if (logContext.Exception != null)
        {
            var exp = logContext.Exception;
            var deep = 0;
            do
            {
                if (deep == 0)
                {
                    sb.Append("\r\n").Append($" {exp.GetType().GetClassFullName()}: {exp.Message}");
                    sb.Append("\r\n").Append($"{exp.StackTrace}");
                }
                else
                {
                    sb.Append("\r\n").Append($" inner{deep}: {exp.GetType().GetClassFullName()}: {exp.Message}");
                    sb.Append("\r\n").Append($"{exp.StackTrace}");
                }

                if (deep < 16)
                {
                    exp = exp.InnerException;
                    deep++;
                }
                else
                {
                    if (exp.InnerException != null)
                    {
                        sb.Append("\r\n").Append($"{" ".Repeat(deep + 1)}太多InnerException,已省略输出...");
                    }
                    break;
                }
            } while (exp != null);
        }
        var msg = sb.ToString();
        //输出格式
        if (LoggerFactory.GetOutFormatFunc != null)
        {
            msg = LoggerFactory.GetOutFormatFunc(new LoggerFactory.SetOutFormatArg
            {
                LogContext = logContext,
                PreMessage = msg
            });
        }
        //自动计算输出文件路径
        var filePath = calcLogPath(null);
        var errorFilePath = string.Empty;
        if (logContext.Level.To<int>() > LoggerLevel.Warning.To<int>())
        {
            errorFilePath = calcLogPath("fail");
        }
        //自定义输出文件路径
        if (LoggerFactory.GetOutFileFunc != null)
        {
            var tmp = LoggerFactory.GetOutFileFunc.Invoke(new LoggerFactory.SetOutFileArg
            {
                BaseDir = LoggerFactory.BaseDir,
                LogContext = logContext,
                PreLogFile = filePath,
                PreErrorLogFile = errorFilePath
            });
            if (tmp.IsNotNullOrEmpty())
            {
                for (int i = 0; i < tmp.Length; i++)
                {
                    var file = tmp[i];
                    if (file.IsNullOrWhiteSpace()) continue;
                    var dir = Path.GetDirectoryName(file);
                    if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                    File.AppendAllLines(file, new[] { msg });
                }
            }
        }
        else
        {
            File.AppendAllLines(filePath, new[] { msg });
            if (errorFilePath.IsNotNullOrWhiteSpace()) File.AppendAllLines(errorFilePath, new[] { msg });
        }

    }

    private static int maxLength = 1024 * 1024 * 10;
    private static string calcLogPath(string prefix)
    {
        var dirPath = LoggerFactory.BaseDir;
        if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
        var fileName = $"commonlog-{DateTime.Now.ToString("yyyyMMdd")}";
        if (prefix.IsNotNullOrWhiteSpace()) fileName = $"commonlog-{prefix}-{DateTime.Now.ToString("yyyyMMdd")}";
        var fileNameWithExt = fileName + ".txt";
        var files = new DirectoryInfo(dirPath).GetFiles(fileName + "*.txt").Select(i => i.Name).ToList();
        var index = calcIndex(files, fileName);
        var fullPath = Path.Combine(dirPath, fileNameWithExt);
        if (index > 0)
        {
            //滚动文件
            fullPath = Path.Combine(dirPath, fileName + ".r" + index + ".txt");
        }
        if (File.Exists(fullPath))
        {
            var fileInfo = new FileInfo(fullPath);
            if (fileInfo.Length > maxLength)
            {
                index++;
                fullPath = Path.Combine(dirPath, fileName + ".r" + index + ".txt");
            }
        }
        return fullPath;
    }

    /// <summary>
    /// 返回当前文件列表的最新序号
    /// </summary>
    /// <param name="files"></param>
    /// <param name="fileName"></param>
    /// <returns></returns>
    private static int calcIndex(List<string> files, string fileName)
    {
        if (files.Count == 0) return 0;
        if (files.Count == 1 && files[0] == fileName) return 0;
        var index = 0;
        foreach (var file in files)
        {
            if (!file.StartsWith(fileName)) continue;
            if (file == fileName + ".txt") continue;
            var list = file.SplitAndTrimTo<string>(".");
            if (list.Count != 3) continue;
            var t = list[1].Replace("r", "").ToWithDefault<int>(0);
            index = Math.Max(index, t);
        }
        return index;
    }
}
